Skip to content

feat(cli): enforce vite-plus import lint rule#1408

Open
Han5991 wants to merge 48 commits intovoidzero-dev:mainfrom
Han5991:issue/1301-import-rule
Open

feat(cli): enforce vite-plus import lint rule#1408
Han5991 wants to merge 48 commits intovoidzero-dev:mainfrom
Han5991:issue/1301-import-rule

Conversation

@Han5991
Copy link
Copy Markdown
Contributor

@Han5991 Han5991 commented Apr 17, 2026

Summary

  • add a bundled Oxlint JS plugin rule that rewrites vite/vitest imports to vite-plus
  • enable the rule by default in generated and migrated lint config
  • cover the new wiring with unit tests and snapshot tests

Testing

  • pnpm exec tsc --noEmit --pretty false
  • pnpm exec tsc --ignoreConfig --noEmit --pretty false --module esnext --moduleResolution bundler --target es2022 --skipLibCheck packages/cli/snap-tests/lint-vite-plus-imports/vite.config.ts
  • local snap tests for command-init-inline-config and lint-vite-plus-imports

Closes #1301
Closes #1457

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 17, 2026

Deploy Preview for viteplus-preview canceled.

Name Link
🔨 Latest commit 97f2191
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/69fddbcf32835b00097acf06

@Han5991 Han5991 force-pushed the issue/1301-import-rule branch from 1fe3184 to 3a43e6d Compare April 17, 2026 08:44
@Han5991 Han5991 marked this pull request as ready for review April 17, 2026 11:51
Comment thread packages/cli/src/oxlint-plugin.ts
Comment thread packages/cli/snap-tests/lint-vite-plus-imports/steps.json
@fengmk2
Copy link
Copy Markdown
Member

fengmk2 commented Apr 17, 2026

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7eceea6afe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/cli/src/oxlint-plugin.ts Outdated
@fengmk2
Copy link
Copy Markdown
Member

fengmk2 commented Apr 17, 2026

Awesome, the import rewrite logic can be completely replaced with the oxlint plugin.

@Han5991
Copy link
Copy Markdown
Contributor Author

Han5991 commented Apr 17, 2026

Awesome, the import rewrite logic can be completely replaced with the oxlint plugin.

Makes sense. I focused this PR on the unsafe rewrite fix first, and I can move the remaining import rewrite logic into the oxlint plugin in a follow-up.

Comment thread packages/cli/src/oxlint-plugin.ts Outdated
Comment thread packages/cli/src/oxlint-plugin.ts Outdated
Comment thread packages/cli/src/oxlint-plugin.ts Outdated
Copy link
Copy Markdown
Contributor

@camc314 camc314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good! but we should definitely use oxlint's types rather than handrolling it, it duplicates code, and might cause issues in future!

@Han5991 Han5991 force-pushed the issue/1301-import-rule branch from b757aaf to 57897c7 Compare April 21, 2026 11:17
@Han5991 Han5991 requested review from camc314 and fengmk2 April 21, 2026 11:18
Comment thread packages/cli/src/oxlint-plugin.ts Outdated
@Han5991
Copy link
Copy Markdown
Contributor Author

Han5991 commented Apr 21, 2026

@fengmk2

The failure appears to come from a version skew in the global snap test.

In this test, vp create and vp check are executed by the locally built Vite+ CLI under test. However, after vp install, the generated application installs vite-plus@latest from the npm registry into its own node_modules.

The local CLI generates a lint config that references vite-plus/oxlint-plugin. When oxlint loads that config, it resolves vite-plus/oxlint-plugin from the generated application's node_modules, not from the local CLI package.

That means oxlint sees the registry-installed vite-plus@latest. That published package does not currently expose the new ./oxlint-plugin subpath, so resolution fails.

This also explains why the other vite-plus/* imports appear to work: those subpaths already exist in the published package, while vite-plus/oxlint-plugin only exists in the local build being tested.

@Han5991
Copy link
Copy Markdown
Contributor Author

Han5991 commented Apr 21, 2026

I think there are three possible ways to address this.

  1. Install the local Vite+ artifact in the generated app

The failure is caused by mixing the locally built CLI under test with vite-plus@latest from the registry. We could make the generated app install the same local Vite+ artifact set as the CLI under test, for example via a packed local tarball.

This avoids the version skew and keeps the test focused on the current build. A packed tarball seems better than link: because it is closer to a real package install and still verifies the published package shape, including package.json#exports.

  1. Resolve the built-in oxlint plugin from the CLI package

Another option is to make vp check resolve Vite+’s built-in oxlint plugin from the CLI package itself before passing the lint config to oxlint.

That would keep the generated app’s registry-installed dependencies intact, but it changes production resolution behavior. I would be careful with this because it may be harder to justify unless built-in Vite+ plugins are explicitly meant to be provided by the running CLI rather than by the project’s installed vite-plus.

  1. Treat this as a registry/local version skew exposed by the snap

We could also leave the test behavior as-is and consider the failure expected until the package containing ./oxlint-plugin is published.

This is the most conservative option, but it means the global snap test can fail whenever the local CLI starts generating config that depends on an unreleased vite-plus subpath.

@Han5991 Han5991 requested a review from camc314 April 21, 2026 23:50
@fengmk2 fengmk2 mentioned this pull request Apr 23, 2026
3 tasks
@Han5991 Han5991 force-pushed the issue/1301-import-rule branch from 7930f65 to 7ab5df7 Compare April 23, 2026 10:19
@Han5991 Han5991 force-pushed the issue/1301-import-rule branch from 359fd37 to 273e4fb Compare April 24, 2026 01:34
@Han5991 Han5991 marked this pull request as draft April 24, 2026 04:52
@Han5991 Han5991 marked this pull request as ready for review April 24, 2026 05:01
return fs.readFileSync(a).equals(fs.readFileSync(b));
}

function assertGlobalCliBinaryMatchesCheckout(binDir: string, casesDir: string): void {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Han5991 Can we create a new GitHub Action to test the oxlint plugin logic? This way we won't need to test through snap test. Currently, because we have to test through snap test, the modification logic for snap test has become too complex.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fengmk2

Yes, I think we can add a dedicated GitHub Action/job for the oxlint plugin integration test. That should be a better place to verify that oxlint can load vite-plus/oxlint-plugin through package exports and that the rule/fix works, instead of relying on snap-test output.

One note: the snap-test changes here were added because the current global snap-test environment can still execute against the previously installed global vite-plus package. In that setup, even if this PR adds the ./oxlint-plugin export, oxlint may resolve vite-plus/oxlint-plugin from the stale installed package instead of the checkout and fail to find the plugin. That is why I forced the global CLI JS entry and relinked installed checkout packages.

I can split this so the oxlint plugin behavior is covered by a focused CI job, and keep only the minimal snap-test fix needed to ensure global snapshots run against the current checkout.

@Han5991 Han5991 force-pushed the issue/1301-import-rule branch from d38d6fc to b6ef1e7 Compare April 24, 2026 13:56
@Han5991
Copy link
Copy Markdown
Contributor Author

Han5991 commented Apr 29, 2026

@fengmk2 @camc314

Started splitting plugin verification into a dedicated CI workflow and hit a tradeoff:

  • migration-* snaps don't run vp lint; the diff is just captured lint-config output. Removing the override infra → still pass.
  • create-framework-shim-astro / vue run vp install + vp check, which actually resolves vite-plus/oxlint-plugin. They only pass today because checkout-packages.json swaps vite-plus for the local pack. Drop that override and they fail until a vite-plus version exposing ./oxlint-plugin is on npm.

Options:

  1. Drop the lint step from framework-shim snaps; rely on the new CI for plugin behavior.
  2. Gate auto-injection of the plugin into generated configs until the export is published.
  3. Keep a minimal vite-plus → local-pack override; new CI on top.
  4. Land this PR after publishing vite-plus with ./oxlint-plugin, then drop the override entirely.

I'd lean (1). Which do you prefer?

Han5991 added 17 commits May 7, 2026 23:12
- Restore SNAP_CASES_DIR env var that fixtures reference for shared helpers
- Restore dashmap entry for rolldown_devtools in Cargo.lock
- Split `vp lint` commands in `snap-tests/lint-vite-plus-imports` to isolate issues in individual files.
- Updated test output to reflect separate error counts for `index.ts` and `types.ts`.
- Bumped several dependencies in `pnpm-lock.yaml` to latest compatible versions.
- Added config flags to `snap-test.ts` for non-interactive runs.
CI rebuilds rolldown_devtools with dashmap as a transitive dependency
and then fails the post-snap-test `git diff Cargo.lock` check. Keep
the entry to match what cargo emits in CI.
The `linkCheckoutPackages: true` mechanism added in 7b14470 only triggers
when the preceding command exits 0. pnpm v11 inserts a placeholder into
`pnpm-workspace.yaml` and exits 1 with ERR_PNPM_IGNORED_BUILDS when a
template pulls in native packages with build scripts (esbuild/sharp from
the astro template). That made `vp install` fail and the symlink swap
never ran, so `vp check --fix` resolved to the npm-registry vite-plus
which lacks the new `./oxlint-plugin` export and crashed during plugin
loading.

Pass `--ignore-scripts` to `vp install` in the astro fixture so the
build-script gate is bypassed without weakening the security model
(safer than `--dangerously-allow-all-builds`). vp check only runs
lint/format and doesn't need the native binaries built. Also drop the
debugging-era pnpm config env vars accidentally landed in d80cc45c.
Reflect the lint plugin config (`jsPlugins` + `rules`) that
`createDefaultVitePlusLintConfig` now writes into vite.config.ts when
the `prefer-vite-plus-imports` rule is enabled.
CI checks out a pinned vite tree that still declares
`tsdown: ^0.21.9` in vite/packages/create-vite and
vite/packages/plugin-legacy, but our lockfile carried `^0.21.10`
(picked up from a stale local vite tree during a prior
non-frozen install). Frozen-lockfile install in CI then aborts
with ERR_PNPM_OUTDATED_LOCKFILE before any build/test runs.

Roll the two specifier lines back to `^0.21.9` to match the
checked-in vite tree without rewriting the resolved versions.
CI clones vite at .upstream-versions.json's pinned hash (32c2978),
which still declares the older specifier set in
vite/packages/vite/package.json. Our lockfile carried newer values
picked up from a stale local vite checkout, so frozen-lockfile
install failed with ERR_PNPM_OUTDATED_LOCKFILE on six packages
(postcss, @vitejs/devtools, @vitest/utils, baseline-browser-mapping,
periscopic, terser).

Roll just the six specifier lines back to match main's lockfile,
without touching the resolved versions or other importer entries.
The previous specifier-only fix wasn't enough — `@vitest/utils` is
declared with an exact specifier, so its resolved version had to move
back to 4.1.4 too, and the same applies to other transitive resolutions
that drifted when our local vite checkout was newer than the pinned
hash.

Locally checked out vite at the pinned hash (.upstream-versions.json
points to 32c2978 = v8.0.10) and ran `pnpm install --no-frozen-lockfile`
so pnpm could re-resolve the affected entries cleanly. Verified with
`pnpm install --frozen-lockfile` (Lockfile is up to date) before
restoring the local vite working tree.
Avoid unnecessary config injection when `baseUrl` is already defined in the project's TypeScript configuration.
@Han5991 Han5991 force-pushed the issue/1301-import-rule branch from ea70476 to f9c2371 Compare May 7, 2026 14:15
@Han5991 Han5991 force-pushed the issue/1301-import-rule branch from bfa8c66 to 2b72c83 Compare May 8, 2026 11:34
Han5991 added 3 commits May 8, 2026 20:37
…`@emnapi/runtime`

Updates references to `@emnapi/core` and `@emnapi/runtime` to version 1.10.0 in the lockfile and removes stale optional dependencies for consistent resolution.
@Han5991
Copy link
Copy Markdown
Contributor Author

Han5991 commented May 8, 2026

Update: musl workaround reapplied with root cause notes

I tried removing the Alpine/musl skip workaround to see what was actually breaking and reproduce it locally, but the failure turned out to be a deeper native binding interaction — not a fixable scope for this PR. So I've reapplied the skip in dac9da1b to keep this PR landable.

What was actually failing

In the Alpine linux-x64-musl E2E job, every vp lint command in lint-vite-plus-imports exits 1 with no output, and vp check --fix in create-framework-shim-vue exits 1 after only the fmt pass line.

Root cause

When oxlint loads vite.config.ts (via the loadVitePlusConfigs path triggered by VP_VERSION), the dynamic import chain pulls in vite-plus@voidzero-dev/vite-plus-core → rolldown's native binding. On musl, loading the rolldown NAPI binding into the same Node.js process that already has the oxlint NAPI binding loaded crashes with SIGSEGV.

Reproduced locally in node:22-alpine3.21:

Step Result
oxlint src/index.ts (no config) exit 0, 0 errors (plugin not loaded — no error visible)
oxlint -c vite.config.ts (with VP_VERSION set) exit 139 (SIGSEGV)
Loading the rolldown binding alone via node -e exit 0 — binding itself is fine

strace confirms +++ killed by SIGSEGV +++ across the worker pool. SIGSEGV is consistent with the CI symptoms: stderr buffer never flushes before the process dies, so the snap test sees [1]> with no body.

Why it's out of scope for this PR

This is a binary-level interaction between two unrelated NAPI modules (@oxlint/binding-* and @rolldown/binding-*) on musl, not anything specific to the new prefer-vite-plus-imports rule. Likely culprits — needs further investigation:

  • Memory allocator conflict (mimalloc / jemalloc statically linked into both)
  • musl static TLS slot exhaustion when two large NAPI modules co-load
  • Shared transitive Rust crate (e.g. swc_*, oxc_*) ABI collision

Suggested follow-up (separate issue)

  1. Reproduce on linux/amd64 to confirm CI parity (I tested on linux/arm64-musl locally)
  2. Capture a coredump / gdb backtrace to identify the exact symbol crashing
  3. Fix upstream (build-flag tweak in oxlint or rolldown) or isolate plugin loading to a subprocess

Happy to file the follow-up issue once this lands.

Same musl NAPI conflict as lint-vite-plus-imports — `vp check --fix`
SIGSEGVs when oxlint loads vite.config.ts with rolldown also in scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Han5991
Copy link
Copy Markdown
Contributor Author

Han5991 commented May 8, 2026

@fengmk2 @camc314
Please review

@Han5991 Han5991 requested a review from fengmk2 May 8, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants